import { BaseComp } from '../../../common/component/comp';
import { ZRenderComp } from '../../../common/component/zrComp';
import Timer from '../../../common/timer/Timer';
import { cubicOut, cubicIn } from '../../../common/animation/index';

import { figures as figurePaths } from '../../../common/paths/fonts/timerFigures0';

/**
 * CosmicView
 * ├── Seat (many)
 * ├── Relief
 * ├── Waterfall
 * │   ├── Stream (many)
 */

class CosmicView extends BaseComp {
  constructor(args) {
    super(null);
    this.context = args.context;
    this.zrender = args.zrender;
    /** @type {HTMLElement} */ this.dom = args.dom;
    this.zr = this.zrender.init(this.dom);
    this.defer(() => this.zr.dispose());
    /** @type {Timer} */ this.timer = args.timer;

    // 计算各个 seat 的位置
    this.computeSeatPostion();

    this.relief = new Relief({
      parent : this,
      zrender : this.zrender,
      zr : this.zr
    });

    this.waterfall = new Waterfall({
      parent : this,
      zrender : this.zrender,
      zr : this.zr,
      left : this.textArea.x,
      top : this.textArea.y,
      width : this.textArea.width,
      height : this.textArea.height,
    });
  }

  computeSeatPostion() {
    /** @type {number} */ const width = this.zr.getWidth();
    /** @type {number} */ const height = this.zr.getHeight();

    let h = height / 2;
    let w = h * 0.8;
    let h2 = h * 0.6;
    let w2 = h2 * 0.8;
    let wsPerSeat = [w2, w2*0.7, w, w, w*0.7, w, w, w*0.7, w];
    let totalW = wsPerSeat.reduce((p, c) => p + c, 0);

    // 说明屏幕不够宽
    if (totalW > width) {
      totalW = width;
      h *= width / totalW;
      w = h * 0.6;
      h2 = h * 0.6;
      w2 = h2 * 0.6;
      wsPerSeat = wsPerSeat.map(ww => ww * width / totalW);
    }
    this.textArea = {
      x : (width - totalW) / 2,
      y : (height - h) / 2,
      width : totalW,
      height : h,
    };

    /** @type {{threshold:number, xs:number[], visuals:boolean[]}[]} */
    this.seatLayouts = [
      [3600000, 9], // >= 60 分. 单位转成 ms
      [600000, 7], // >= 10 分.
      [60000, 6], // >= 60 秒.
      [-1, 4], // 其它
    ].map(args => {
      const [ threshold, showIndex ] = args;
      const visuals = new Array(9).fill(true).map((_, index) => index < showIndex);
      return {
        threshold,
        xs: new Array(9).fill(0),
        visuals
      };
    });

    this.seatLayouts.forEach(pos => {
      let totalW2 = pos.visuals
        .map((visual, index) => { return {visual, index}; })
        .filter(args => args.visual)
        .map(args => wsPerSeat[args.index])
        .reduce((p, c) => p + c, 0);
      let marginRight = (width - totalW2) / 2;
      let xx = width - marginRight;
      for (let i = 0; i < pos.xs.length; i++) {
        if (!pos.visuals[i]) continue;
        xx -= wsPerSeat[i];
        pos.xs[i] = xx + wsPerSeat[i] / 2;
      }
    });

    const elTest = new TestSeat({
      parent: this,
      zr : this.zr,
      zrender : this.zrender,
    });
    const boundingRect = elTest.boundingRect;
    const scale = h / boundingRect.height;
    const offX = -boundingRect.x - boundingRect.width / 2;
    const offY = -boundingRect.y - boundingRect.height;

    this.currentSeatLayout = this.seatLayouts[0];
    this.seatLayouts.sort((a, b) => a.threshold - b.threshold);

    // seats 顺位是倒的
    // [0]: 0.1 秒
    // [1]: 小数点
    // [2]: 秒
    // [3]: 10 秒
    // [4]: 冒号    | 当 >= 60 秒时出现
    // [5]: 分      | 当 >= 60 秒时出现
    // [6]: 10 分   | 当 >= 10 分时出现
    // [7]: 冒号    | 当 >= 60 分时出现
    // [8]: 小时    | 当 >= 60 分时出现
    this.seats = new Array(9).fill().map((_, index) => new Seat({
      parent : this,
      zr : this.zr,
      zrender : this.zrender,
      x : this.currentSeatLayout.xs[index],
      y : h + (height - h) / 2,
      scale : index < 2 ? scale * 0.6 : scale,
      offX, offY,
    }));

    [
      ['0','1','2','3','4','5','6','7','8','9'],
      ['.'],
      ['0','1','2','3','4','5','6','7','8','9'],
      ['0','1','2','3','4','5','6','7','8','9'],
      [':'],
      ['0','1','2','3','4','5','6','7','8','9'],
      ['0','1','2','3','4','5','6','7','8','9'],
      [':'],
      ['0','1','2','3','4','5','6','7','8','9'],
    ].forEach((chs, index) => this.seats[index].loadFigures(chs));

    this.seats[0].enableShowAnimation = false;
  }

  tick() {
    const { timer } = this;
    timer.tick();

    let current = this.seatLayouts[0];
    for (let i = 1; i < this.seatLayouts.length; i++) {
      let layout = this.seatLayouts[i];
      if (timer.delta >= layout.threshold) {
        current = layout;
      } else {
        break;
      }
    }

    if (current != this.currentSeatLayout) {
      this.currentSeatLayout = current;

      this.seats.forEach((seat, index) => {
        if (!current.visuals[index]) {
          seat.hide();
          return;
        }

        seat.show();
        seat.x = current.xs[index];
      });
    }

    this.seats.filter(seat => seat.isShow()).forEach((seat, index) => {
      switch (index) {
        case 0: seat.show(timer.ds + ''); break;
        case 1: seat.show('.'); break;
        case 2: seat.show(((timer.s % 10) | 0) + ''); break;
        case 3: seat.show(((timer.s / 10) | 0) + ''); break;
        case 4: case 7: seat.show(':'); break;
        case 5: seat.show(((timer.m % 10) | 0) + ''); break;
        case 6: seat.show(((timer.m / 10) | 0) + ''); break;
        case 8: seat.show(((timer.h % 10) | 0) + ''); break;
      }
    });
  }
}

class TestSeat extends ZRenderComp {
  constructor(args) {
    super(args.parent, args.zrender);
    this.useExistZRenderInstance(args.zr, 0, 0);
    const sp = this.createPathFromString(figurePaths['0'])
      .fill('#47A')
      .opacity(0)
      .build();
    this.boundingRect = sp.getBoundingRect();
  }
}

class Seat extends ZRenderComp {
  constructor(args) {
    super(args.parent, args.zrender);
    // 锚点位置在 seat 正下, 输中间对齐
    this.useExistZRenderInstance(args.zr, args.x, args.y);
    /** @type {number} */ this.scale = args.scale;
    /** @type {number} */ this.offX = args.offX;
    /** @type {number} */ this.offY = args.offY;
    /** @type {string} */ this.lastCh = null;
    this.enableShowAnimation = true;
    
    this.on('timer/stop', () => this.onTimerStop());
  }

  /**
   * @param {string[]} chs 
   */
  loadFigures(chs) {
    this.sps = chs.map(ch => {
      let sp = this.createPathFromString(figurePaths[ch])
        .fill('#9DF')
        .scale(this.scale)
        .position(this.offX * this.scale, this.offY * this.scale)
        .shadow('#FFF', 20, 0, 0)
        .opacity(0)
        .build();
      return { name: ch, sp };
    });
    this.sps[0].sp.style().opacity(1).update();
    this.lastCh = this.defCh = chs[0];

    // 下面这个是 timer 结束时触发的效果
    this.sps2 = chs.map(ch => {
      let sp = this.createPathFromString(figurePaths[ch])
        .fillNone()
        .stroke('#FFF', 3 / this.scale)
        .scale(this.scale)
        .position(this.offX * this.scale, this.offY * this.scale)
        .opacity(0)
        .build();
      return { name: ch, sp };
    });
  }

  hide() {
    this.sps.forEach(sp => sp.sp.style().opacity(0).update());
    this.lastCh = null;
  }

  isShow() {
    return this.lastCh !== null;
  }

  show(ch = this.defCh) {
    if (ch == this.lastCh) return;

    this.sps.filter(sp => this.lastCh == sp.name).forEach(o => o.sp.style().opacity(0).update());

    if (this.enableShowAnimation) {
      const list = this.sps.filter(sp => ch == sp.name);
      list.forEach(o => o.sp.style().opacity(0.5).update());
      this.createAnimation('enter')
        .whenUpdated(p => {
          list.forEach(o => {
            o.sp.style().opacity(p.value).update();
            o.sp.moveTo(o.sp.x, (p.value) * this.offY * this.scale);
          });
        })
        .update()
        .when(0, 0.5)
        .when(120, 1).easing(cubicOut)
        .commit();
    } else {
      this.sps.filter(sp => ch == sp.name).forEach(o => o.sp.style().opacity(1).update());
    }
    this.lastCh = ch;
  }

  onTimerStop() {
    this.clearAnimations(name => name.startsWith('timerStop'));
    this.sps2.forEach(o => o.sp.style().opacity(0).update());

    const sp = this.sps2.filter(sp => this.lastCh == sp.name)[0];
    if (!sp) {
      return;
    }

    this.createAnimation('timerStop1')
      .whenUpdated(p => sp.sp.style().opacity(p.value).update())
      .update()
      .when(0, 1)
      .when(400, 1)
      .when(1000, 0).easing(cubicOut)
      .commit();
    this.createAnimation('timerStop2')
      .whenUpdated(p => sp.sp.style()
        .lineDash([p.value * 600, 600 - p.value * 600], p.value * 300)
        .shadow('#FFF', p.value * 100, 0, 0)
        .stroke('#FFF', p.value * 15 / this.scale)
        .update()
      )
      .update()
      .when(0, 0)
      .when(1000, 1).easing(cubicOut)
      .commit();
  }
}

/**
 * "暂停中" 这个文字的位置需要查看 view.seat 的位置, 这里有隐形的依赖关系
 */
class Relief extends ZRenderComp {
  constructor(args) {
    super(args.parent, args.zrender);
    this.useExistZRenderInstance(args.zr, 0, 0);

    this.spImg = this.createImage(this.generateImage())
      .blend('source-atop')
      .z(0, 1)
      .opacity(0)
      .build();
    this.on('timer/start', () => this.onTimerStart());
    this.on('timer/pause', () => this.onTimerPause());
    this.on('timer/stop', () => this.onTimerStop());
    this.on('timer/reset', () => this.onTimerStop());

    const w = this.zr.getWidth(), h = this.zr.getHeight();
    this.spText = this.createText('暂停中.....', h / 4)
      .blend('source-atop')
      .textFill('#FFF')
      .textStyle('right', 'bottom', 'bold', 'Microsoft YaHei UI')
      .z(0, 22)
      .position(w / 2, h * 0.8)
      .opacity(0)
      .build();

    this.imgOpacity = 0;
    this.paused = false;
  }

  generateImage() {
    const image = document.createElement('canvas');
    const w = image.width = this.zr.getWidth();
    const h = image.height = this.zr.getHeight();
    const padding = this.padding = (w / 12) | 0;
    const ctx = image.getContext('2d');

    const data = ctx.getImageData(0, 0, w, h);
    const array = data.data;

    for (let x = 0; x < w; x++) {
      for (let y = 0; y < h; y++) {
        let i = (y * w + x) * 4;
        array[i    ] = 51;
        array[i + 1] = 188;
        array[i + 2] = 255;

        let yy = Math.min(y, h-y);
        array[i + 3] = (x - yy / 2 + padding * 5) % padding / padding * 255;
      }
    }

    ctx.putImageData(data, 0, 0);
    return image;
  }

  tick(now) {
    super.tick(now);

    /** @type {Timer} */ const timer = this.root.timer;
    if (timer.isPaused()) {
      this.spText.style().opacity(Math.abs((now % 2000) / 1000 - 1)).update();
    } else if (timer.isPlaying()) {
      this.spImg.moveTo(timer.delta % 1500 / 1500 * this.padding);
    }
  }

  onTimerStart() {
    this.createAnimation('timerStartOpacity')
        .whenUpdated(p => this.spImg.style().opacity(this.imgOpacity = p.value).update())
        .update()
        .when(0, this.imgOpacity)
        .when(500, 1).easing(cubicOut)
        .commit();
    this.spText.style().opacity(0).update();
    this.paused = false;
  }

  onTimerPause() {
    this.clearAnimations(name => name === 'timerStartOpacity');
    this.paused = true;

    this.spText.moveTo(this.root.currentSeatLayout.xs[0]);
    this.spText.style().opacity(0.5).update();
  }

  onTimerStop() {
    this.clearAnimations(name => name === 'timerStartOpacity');
    this.spImg.style().opacity(this.imgOpacity = 0).update();
    this.spText.style().opacity(0).update();
    this.paused = false;
  }
}

/**
 * top, left, width, height 所指的区域是 Stream 产生的区域
 */
class Waterfall extends ZRenderComp {
  constructor(args) {
    super(args.parent, args.zrender);
    this.useExistZRenderInstance(args.zr, 0, 0);
    /** @type {number} */ this.top = args.top;
    /** @type {number} */ this.left = args.left;
    /** @type {number} */ this.width = args.width;
    /** @type {number} */ this.height = args.height;

    this.spGlow = this.createRect(this.width, this.height / 4)
      .fillLinearGradient(0, 0, 0, 1, [
        {offset:0, color:'#FFFF'},
        {offset:1, color:'#FFF0'}
      ])
      .opacity(0)
      .blend('source-atop')
      .position(this.left, this.top + this.height)
      .z(0, 10)
      .build();
    this.on('timer/pause', () => this.onTimerPause());
    this.on('timer/stop', () => this.onTimerPause());
    this.on('timer/reset', () => this.onTimerPause());
  }

  tick(now) {
    super.tick(now);

    /** @type {Timer} */ const timer = this.root.timer;
    if (!timer.isPlaying()) return;

    const y = (1 - (timer.delta % 60000) / 60000) * this.height + this.top;
    for (let i = 0; i < 5; i++) {
      new Stream({
        parent : this,
        zrender : this.zrender,
        zr : this.zr,
        x : this.left + Math.random() * this.width, y,
        size : this.height / 20 + Math.random() * (this.height / 20),
      });
    }

    this.spGlow.moveTo(undefined, y);
    this.spGlow.style().opacity(Math.abs((timer.delta % 2000) / 1000 - 1) * 0.6).update();
  }

  onTimerPause() {
    this.spGlow.style().opacity(0).update();
  }
}

class Stream extends ZRenderComp {
  constructor(args) {
    super(args.parent, args.zrender);
    this.useExistZRenderInstance(args.zr, args.x, args.y);
    this.size = args.size;
  }

  init() {
    const {size} = this;
    const s = (size / 2) | 0;
    const path = `M${s},${s}V-${s}H-${s}V${s}Z`;
    this.path = path;

    const length = ((Math.random() * 3) | 0) + 3;
    this.sps = new Array(length).fill(0).map(() => this.createPathFromString(path)
      .fill('#FFE')
      .blend('source-atop')
      .opacity(0)
      .scale(0)
      .z(0, 11)
      .build());
    this.sps.forEach((sp, index) => {
      const delay = index * 90;
      this.createAnimation('s' + index)
        .whenUpdated(p => {
          sp.style()
            .scale(p.value)
            .opacity(p.value / 3)
            .update();
        })
        .update()
        .when(delay, 0)
        .when(200 + delay, 0.8).easing(cubicOut)
        .when(1500 + delay, 0).easing(cubicIn).callback(index == length - 1 ? () => this.destroy() : null)
        .commit();
      this.createAnimation('y' + index)
        .whenUpdated(p => sp.moveTo(undefined, p.value * 6).rotation(Math.PI * p.value / size))
        .update()
        .when(200 + delay, 0)
        .when(1500 + delay, size).easing(cubicIn)
        .commit();
    });
  }
}

export {
  CosmicView,
};
